package net.sashag.nadlandroid;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.apps.analytics.easytracking.TrackedMapActivity;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.googlecode.androidannotations.annotations.AfterViews;
import com.googlecode.androidannotations.annotations.Background;
import com.googlecode.androidannotations.annotations.EActivity;
import com.googlecode.androidannotations.annotations.Extra;
import com.googlecode.androidannotations.annotations.NoTitle;
import com.googlecode.androidannotations.annotations.OptionsItem;
import com.googlecode.androidannotations.annotations.OptionsMenu;
import com.googlecode.androidannotations.annotations.SystemService;
import com.googlecode.androidannotations.annotations.UiThread;
import com.googlecode.androidannotations.annotations.ViewById;
import com.googlecode.androidannotations.annotations.sharedpreferences.Pref;
import com.parse.FindCallback;
import com.parse.ParseException;
import com.parse.ParseGeoPoint;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import com.parse.ParseQuery.CachePolicy;
import com.parse.ParseUser;

@EActivity(R.layout.aptlisting)
@NoTitle
@OptionsMenu(R.menu.aptmap_menu)
public class ApartmentListingActivity extends TrackedMapActivity {
	
	private static final long LOCATION_UPDATE_TIME = 10000;			   //in milliseconds
	private static final float LOCATION_UPDATE_DISTANCE = 50.0f;	   //in meters
	private static final long LOCATION_FRESHNESS = 30000;			   //in milliseconds
	private static final int MIN_PRICE_DEFAULT = 10000;
	private static final int MAX_PRICE_DEFAULT = 20000;

	private Map<String, Region> regions;
	private Region lastSearchRegion;
	private Location lastSearchLocation;
	
	@Pref protected NadlandroidPreferences_ preferences; 
	
	private MapView mapApartments;
	@ViewById protected LinearLayout llPlaceholder;
	
	@Extra("AroundMe")
	protected boolean searchAroundMe;
	
	@SystemService
	protected LocationManager locationManager;
	
	private ProgressDialog waitDialog;
	private LocationListener locationListener;
	private ApartmentOverlay apartmentOverlay;
	private MyLocationOverlay myLocationOverlay;
	
	@Override
	protected void onResume() {
		myLocationOverlay.enableMyLocation();
		super.onResume();
	}
	
	@Override
	protected void onPause() {
		myLocationOverlay.disableMyLocation();
		super.onPause();
	}
	
	@AfterViews
	protected void listingSetup() {
		mapApartments = MapViewHelper.createMapView(this, llPlaceholder);
		mapApartments.setClickable(true);
		mapApartments.setTraffic(true);
		mapApartments.getController().setZoom(14);
		apartmentOverlay = new ApartmentOverlay(this, getResources().getDrawable(R.drawable.apartmentoverlay));
		mapApartments.getOverlays().add(apartmentOverlay);
		myLocationOverlay = new MyLocationOverlay(this, mapApartments);
		mapApartments.getOverlays().add(myLocationOverlay);
		if (searchAroundMe) {
			Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
			if (lastKnownLocation != null &&
				(System.currentTimeMillis() - lastKnownLocation.getTime()) < LOCATION_FRESHNESS) {
				//We have up to date GPS location information, this is ideal, just start the search:
				showWaitDialog("We are waiting for the search results from our Web service...");
				doSearchAroundLocation(lastKnownLocation);
			} else {
				startWaitingForLocation();
			}
		} else {
			showWaitDialog("We are waiting for the search results from our Web service...");
			ParseObject searchPreferences = ParseUser.getCurrentUser().getParseObject("SearchPreferences");
			if (searchPreferences == null) {
				populateRegionsThenAskForPreferences();
			} else {
				promptForUsePreferences(searchPreferences);
			}
		}
	}
	
	private void promptForUsePreferences(final ParseObject searchPreferences) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle("Use Saved Preferences?");
		builder.setMessage("Should we use your recent search preferences for this search?");
		builder.setCancelable(false);
		builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				doSearchOnPreferences(searchPreferences);
			}
		});
		builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				populateRegionsThenAskForPreferences();
			}
		});
		builder.show();
	}
	
	private void startWaitingForLocation() {
		locationListener = new LocationListener() {
			public void onStatusChanged(String provider, int status, Bundle extras) {}
			public void onProviderEnabled(String provider) {}
			public void onProviderDisabled(String provider) {}
			public void onLocationChanged(Location location) {
				locationManager.removeUpdates(locationListener);
				locationListener = null;
				mapApartments.getController().animateTo(new GeoPoint(
						(int)(location.getLatitude()*1E6), (int)(location.getLongitude()*1E6)));
				doSearchAroundLocation(location);
			}
		};
		//TODO: For now, using network provider because it makes testing easier, but should
		//		think about the best way to combine information -- users might want accurate
		//		search around GPS-based location, or is it not important?
		locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
				LOCATION_UPDATE_TIME, LOCATION_UPDATE_DISTANCE, locationListener);
		showWaitDialog("Waiting for location to become available...");
	}

	private void showWaitDialog(String message) {
		waitDialog = new ProgressDialog(this);
		waitDialog.setTitle("Please wait");
		waitDialog.setMessage(message);
		waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		waitDialog.setOnCancelListener(new OnCancelListener() {
			public void onCancel(DialogInterface dialog) {
				//If the wait dialog was cancelled, it means the user is not going to be interested
				//in the actual search results, so we can quit this activity completely.
				finish();
			}
		});
		waitDialog.show();
	}
	
	private void dismissWaitDialog() {
		waitDialog.dismiss();
	}
	
	@Override
	protected void onStop() {
		super.onStop();
		if (locationListener != null) {
			locationManager.removeUpdates(locationListener);
			locationListener = null;
		}
	}
	
	@Background
	protected void populateRegionsThenAskForPreferences() {
		populateRegions();
		askForPreferences();
	}

	private void populateRegions() {
		try {
			regions = new HashMap<String, Region>();
			Region[] allRegions = Region.getAllRegions();
			for (Region region : allRegions) {
				regions.put(region.getName(), region);
			}
		} catch (ParseException e) {
			Log.e("ApartmentListingActivity", "Could not retrieve regions: " + e.getMessage());
		}
	}

	@UiThread
	protected void askForPreferences() {
		final Dialog preferencesDialog = new Dialog(this);
		preferencesDialog.setTitle("Search Preferences");
		preferencesDialog.setContentView(R.layout.searchprefs);
		preferencesDialog.setOnCancelListener(new OnCancelListener() {
			public void onCancel(DialogInterface dialog) {
				finish(); //We can't continue without the user's preferences
			}
		});
		
		final SeekBar seekMinPrice = (SeekBar)preferencesDialog.findViewById(R.id.barMinimumPrice);
		final TextView txtCurrMinPrice = (TextView)preferencesDialog.findViewById(R.id.txtCurrentMinPrice);
		seekMinPrice.setMax(MIN_PRICE_DEFAULT);
		seekMinPrice.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
			public void onStopTrackingTouch(SeekBar seekBar) {}
			public void onStartTrackingTouch(SeekBar seekBar) {}
			public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
				txtCurrMinPrice.setText(Integer.toString(progress));
			}
		});
		seekMinPrice.setProgress(1500);
		
		final SeekBar seekMaxPrice = (SeekBar)preferencesDialog.findViewById(R.id.barMaximumPrice);
		final TextView txtCurrMaxPrice = (TextView)preferencesDialog.findViewById(R.id.txtCurrentMaxPrice);
		seekMaxPrice.setMax(MAX_PRICE_DEFAULT);
		seekMaxPrice.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
			public void onStopTrackingTouch(SeekBar seekBar) {}
			public void onStartTrackingTouch(SeekBar seekBar) {}
			public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
				txtCurrMaxPrice.setText(Integer.toString(progress));
			}
		});
		seekMaxPrice.setProgress(6000);
		
		final Spinner spinBedrooms = (Spinner)preferencesDialog.findViewById(R.id.spnBedrooms);
		ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this, android.R.layout.simple_spinner_item,
				new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7 });
		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		spinBedrooms.setAdapter(adapter);
		spinBedrooms.setSelection(2);
		
		final AutoCompleteTextView edtRegion = (AutoCompleteTextView)preferencesDialog.findViewById(R.id.edtRegion);
		edtRegion.setAdapter(new ArrayAdapter<String>(
				this, android.R.layout.simple_dropdown_item_1line,
				regions.keySet().toArray(new String[regions.size()])));
		
		Button searchButton = (Button)preferencesDialog.findViewById(R.id.btnSearch);
		searchButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				preferencesDialog.dismiss();
				ParseObject searchPreferences = new ParseObject("SearchPreferences");
				searchPreferences.put("Region", edtRegion.getText().toString());
				searchPreferences.put("MinPrice", seekMinPrice.getProgress());
				searchPreferences.put("MaxPrice", seekMaxPrice.getProgress());
				searchPreferences.put("Bedrooms", (int)(Integer)spinBedrooms.getSelectedItem());
				searchPreferences.saveInBackground();
				ParseUser.getCurrentUser().put("SearchPreferences", searchPreferences);
				ParseUser.getCurrentUser().saveInBackground();
				doSearchOnPreferences(searchPreferences);
			}
		});
		preferencesDialog.show();
	}
	
	@Background
	protected void doSearchOnPreferences(ParseObject searchPreferences) {
		ParseQuery apartmentQuery = new ParseQuery("Apartment");
		try {
			searchPreferences = searchPreferences.fetchIfNeeded();
		} catch (ParseException e) {
			populateRegionsThenAskForPreferences();
			return;
		}
		apartmentQuery.setCachePolicy(CachePolicy.CACHE_ELSE_NETWORK);
		apartmentQuery.setMaxCacheAge(5*60*1000 /*five minutes*/);
		apartmentQuery.whereEqualTo("Bedrooms", searchPreferences.getInt("Bedrooms"));
		apartmentQuery.whereGreaterThanOrEqualTo("Price", searchPreferences.getInt("MinPrice"));
		apartmentQuery.whereLessThanOrEqualTo("Price", searchPreferences.getInt("MaxPrice"));
		apartmentQuery.whereEqualTo("Published", true);
		
		String searchRegion = searchPreferences.getString("Region");
		//If the user picked a region, limit the search to the region's bounding box.
		if (searchRegion != null && searchRegion.length() > 0) {
			if (regions == null) //In case the search was started from saved preferences
				populateRegions();
			lastSearchRegion = regions.get(searchRegion);
			apartmentQuery.whereWithinGeoBox("Location", lastSearchRegion.getSouthWest(), lastSearchRegion.getNorthEast());
		}
		
		issueQuery(apartmentQuery);
	}
	
	private void doSearchAroundLocation(Location location) {
		ParseGeoPoint desiredLocation = new ParseGeoPoint(location.getLatitude(), location.getLongitude());
		ParseQuery apartmentQuery = new ParseQuery("Apartment");
		apartmentQuery.whereWithinKilometers("Location", desiredLocation, Float.parseFloat(preferences.locationRadius().get()));
		apartmentQuery.whereEqualTo("Published", true);
		lastSearchRegion = null;
		lastSearchLocation = location;
		issueQuery(apartmentQuery);	
	}

	@UiThread
	protected void issueQuery(ParseQuery apartmentQuery) {
		apartmentQuery.findInBackground(new FindCallback() {
			public void done(List<ParseObject> results, ParseException e) {
				if (e != null) {
					Toast.makeText(ApartmentListingActivity.this,
							"Error retrieving apartments: " + e.toString(), Toast.LENGTH_LONG).show();
					finish();
					return;
				}
				displaySearchResults(results);
			}
		});
	}
	
	private void displaySearchResults(List<ParseObject> results) {
		dismissWaitDialog();
		if (results.size() == 0) {
			Toast.makeText(this,
					"There are no results for your query. Try a less specific search.", Toast.LENGTH_LONG).show();
			finish();
		}
		apartmentOverlay.clearApartments();
		for (ParseObject apartmentObject : results) {
			Apartment apartment = Apartment.fromParseObject(apartmentObject);
			apartmentOverlay.addApartment(apartment);
		}
		mapApartments.invalidate();
		centerMap();
	}

	private void centerMap() {
		if (lastSearchRegion == null && lastSearchLocation != null) {
			//This was a search around a location, so focus the map on it.
			mapApartments.getController().setCenter(new GeoPoint(
					(int)(lastSearchLocation.getLatitude()*1E6),
					(int)(lastSearchLocation.getLongitude()*1E6)));
		} else if (lastSearchRegion != null) {
			//This was a search around a region, so try heuristics to center the map
			//around it to a certain extent.
			double midLatitude = (lastSearchRegion.getSouthWest().getLatitude() + lastSearchRegion.getNorthEast().getLatitude())/2;
			double midLongitude = (lastSearchRegion.getSouthWest().getLongitude() + lastSearchRegion.getNorthEast().getLongitude())/2;
			GeoPoint northEast = new GeoPoint((int)(midLatitude*1E6), (int)(midLongitude*1E6));
			mapApartments.getController().setCenter(northEast);
		}
		//Otherwise, it was a regionless search, not around a location. Nothing to center on.
	}

	@OptionsItem(R.id.show_grid_menu_item)
	protected void onShowGridClicked() {
		new GridApartmentListingActivity_.IntentBuilder_(this)
			.title("Details")
			.apartments(apartmentOverlay.getApartments())
			.isShowingFavorites(false)
			.start();
	}
	
	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}
}
